#include "AssetWriter.h"

#include "Path/Paths.h"

#include "Serialize/BinarySerializer.h"
#include "Async/PackagedTask.h"

#include "Containers/Containers.h"

#include "Asset/AssetSubsystem.h"

#include <fstream>
#include <string.h>

std::shared_ptr<CPackagedTask<void>> CAssetWriter::CreateAsyncWriteTask(const TVector<uint8_t>& InAssetFileData, const SAssetPath &InAssetPath, bool bOnlyAssetHeader)
{
    return CreateAsyncWriteTask(
        InAssetFileData
        , InAssetPath.GetPackageName()
        , InAssetPath.GetFolderPathInPackage()
        , InAssetPath.GetAssetName()
        , bOnlyAssetHeader);
}

std::shared_ptr<CPackagedTask<void>> CAssetWriter::CreateAsyncWriteTask(const TVector<uint8_t> &InAssetFileData, const CString &InPackageName, const CString &InAssetPathInPackage, const CString &InAssetName, bool bOnlyAssetHeader)
{
    auto Task=WH::CreateAsyncTask<void>([InPackageName,InAssetPathInPackage,InAssetName,bOnlyAssetHeader,InAssetFileData](bool&) -> void
    {
        //1.find Package folder
        SPath PackageFolder= RAssetSubsystem::Get()->FindPackage(InPackageName).Path;

        if(!PackageFolder.Exists())
        {
            assert(false&& "package folder not exist");
        }

        //2.find asset file
        SPath AssetFile = PackageFolder / InAssetPathInPackage /(InAssetName +U".asset");
        if(bOnlyAssetHeader && !AssetFile.Exists())
        {
            assert(false&& "asset file not exist");
        }

        if(!bOnlyAssetHeader)
        {
            //create folders if not exist
            if(!AssetFile.Parent().Exists())
            {
                std::filesystem::create_directories(AssetFile.Parent().GetLowLevel());
            }


            //3 just  write to the file
            std::ofstream File(AssetFile.GetLowLevel(), std::ios::binary);
            if(!File.is_open())
            {
                //open file failed
                assert(false &&"Load file failed");
                return;
            }

            File.write((char*)InAssetFileData.Data(), InAssetFileData.Num());
            return;
        }

        //4.read asset file non header part into memory
        TVector<uint8_t> FileNonHeaderPart;
        {
            std::ifstream File(AssetFile.GetLowLevel(), std::ios::ate | std::ios::binary);
            if(!File.is_open())
            {
                assert(false &&"Load file failed");
                return ;
            }

            //at the begining of the file, there is a uint64_t to store the asset header size
            uint64_t AssetHeaderSize = 0;  
            {
                TVector<uint8_t> FileHeadSizeData;
                FileHeadSizeData.Resize(sizeof(uint64_t));
                File.read((char*)FileHeadSizeData.Data(), sizeof(uint64_t));

                CBinaryDeserializer Serializer(FileHeadSizeData);
                Serializer >> AssetHeaderSize;

                File.seekg(0);
            }

            //the loaded data is the asset header
            File.read((char*)FileNonHeaderPart.Data(), FileNonHeaderPart.Num());
        }

        //5.write asset file
        TVector<uint8_t> NewFileContent;
        {
            //get new header size
            uint64_t AssetHeaderSize ;
            {
                CBinaryDeserializer Serializer(InAssetFileData);
                Serializer >> AssetHeaderSize;
            }

            NewFileContent.Resize(AssetHeaderSize + FileNonHeaderPart.Num());

            //copy header
            memcpy(NewFileContent.Data(), InAssetFileData.Data(), AssetHeaderSize);

            //copy non header part
            memcpy(NewFileContent.Data() + AssetHeaderSize, FileNonHeaderPart.Data(), FileNonHeaderPart.Num());

        }

        {
            std::ofstream File(AssetFile.GetLowLevel(), std::ios::binary);
            if(!File.is_open())
            {
                //open file failed
                assert(false &&"Load file failed");
                return;
            }

            File.write((char*)NewFileContent.Data(), NewFileContent.Num());

        }

    });

    return Task;    
}

std::shared_ptr<CPackagedTask<TTuple<TOptional<CString>, SAssetPath, CString>>> 
    CAssetWriter::CreateAsyncCreateAssetPathTask(
        CClass *InClassName
        , const CString &InPackageName
        , const CString &InQueryFolderRelativePath
        , const CString& InAssetName
        )
{
    auto CreateAssetPathTask = WH::CreateAsyncTask<TTuple<TOptional<CString>, SAssetPath, CString>>(
        [InClassName,InPackageName,InQueryFolderRelativePath,InAssetName](bool& bSucceed) 
            -> TTuple<TOptional<CString>, SAssetPath, CString>
    {
        //1.find Package folder
        SPath PackageFolder= RAssetSubsystem::Get()->FindPackage(InPackageName).Path;

        SPath AssetParentFolder=PackageFolder/InQueryFolderRelativePath;

        if(!AssetParentFolder.Exists())
        {
            bSucceed=false;
            return WH::MakeTuple<TOptional<CString>, SAssetPath, CString>(
                TOptional<CString>(U"Asset parent folder not exist")
                ,SAssetPath()
                ,CString()
                );
        }

        //2.make a avaliable asset path
        CString FinalAssetName;
        SPath AssetPath;
        {
            int32_t AssetIndex = 0;
            do
            {
                FinalAssetName =InAssetName + 
                    (AssetIndex == 0 ? CString(U"") : CString(AssetIndex));
                AssetPath = AssetParentFolder / (FinalAssetName + U".asset");
                ++AssetIndex;
            } while (AssetPath.Exists());
        }


        return 
            WH::MakeTuple<TOptional<CString>, SAssetPath, CString>(
                TOptional<CString>()
                ,SAssetPath(
                    InClassName
                    ,InPackageName
                    ,InQueryFolderRelativePath
                    ,FinalAssetName)
                ,AssetPath.GetBaseFileName()
                );
            
    });

    return CreateAssetPathTask;

}
